Signal Handling
![Fairuz [FZ]](https://github.com/nargafrz.png)
Pengertian Signal
Signal dalam sistem operasi adalah suatu bentuk interupsi perangkat lunak yang dikirim ke proses. Sistem operasi menggunakan sinyal untuk melaporkan kejadian atau situasi yang membutuhkan perhatian program yang sedang berjalan. Setiap jenis sinyal umumnya mewakili suatu kondisi tertentu – beberapa sinyal menandakan terjadinya error (misalnya akses memori tidak valid), sementara sinyal lain mewakili event asynchronous seperti interupsi dari pengguna atau perangkat keras. Contoh sinyal error adalah SIGSEGV
yang terjadi saat program mengakses alamat memori ilegal, sedangkan contoh sinyal asynchronous adalah sinyal yang dikirim saat koneksi terminal terputus atau pengguna menekan tombol tertentu. ika suatu kejadian yang menyebabkan sinyal dapat diprediksi, program dapat menetapkan handler (fungsi penanganan) untuk sinyal tersebut dan memberi tahu OS agar menjalankan handler tersebut ketika sinyal yang dimaksud terjadi.
Perbedaan Sinyal Normal vs Real-Time
Secara umum, terdapat dua kategori sinyal di sistem operasi seperti UNIX/Linux, yaitu sinyal standar (normal) dan sinyal real-time. Linux mendukung kedua jenis sinyal tersebut: POSIX standard (reliable) signals dan POSIX real-time signals. Sinyal standar adalah sinyal-sinyal yang telah lama ada dan memiliki makna serta perilaku bawaan yang ditetapkan oleh standar POSIX (misalnya SIGINT
, SIGTERM
, dll), sedangkan sinyal real-time diperkenalkan kemudian melalui ekstensi POSIX.1b untuk memenuhi kebutuhan komunikasi yang lebih kompleks pada sistem real-time.
Perbedaan utama antara sinyal standar dan sinyal real-time terletak pada semantik pengirimannya. Pada sinyal standar, jika jenis sinyal yang sama terkirim beberapa kali saat sinyal tersebut sedang diblokir, hanya satu instance sinyal yang akan ditangani setelah unblocking (sinyal standar tidak mengantri multiple instance). Sebaliknya, sinyal real-time dapat mengantri – artinya beberapa instance sinyal real-time yang sama dapat dikumpulkan dalam antrean dan akan dikirimkan satu per satu sesuai jumlah pengirimannya.
Selain itu, sinyal real-time memungkinkan pengiriman data tambahan bersama sinyal. Jika sinyal real-time dikirim menggunakan fungsi sigqueue()
, pengirim dapat menyertakan nilai integer atau pointer sebagai data dalam sinyal. Proses penerima yang menangani sinyal real-time dengan flag SA_SIGINFO
(melalui sigaction
) dapat memperoleh data tersebut melalui struktur siginfo_t
yang diberikan ke handler. Fitur ini tidak tersedia untuk sinyal standar.
Dari segi urutan dan prioritas, sinyal real-time memiliki jaminan urutan pengiriman. Jika beberapa sinyal real-time dikirim ke sebuah proses, sinyal tersebut akan diterima sesuai urutan pengirimannya (dan jika jenisnya berbeda, akan diurutkan berdasarkan nomor sinyal terendah lebih dulu). Sebaliknya, untuk beberapa sinyal standar yang tertunda, urutan pengirimannya ke proses tidak ditentukan (unspecified) oleh POSIX. Perlu dicatat bahwa jika terdapat sinyal standar dan real-time menunggu pada saat yang sama, POSIX tidak menentukan mana yang harus diantar lebih dulu, namun implementasi Linux memberi prioritas pada sinyal standar dibanding real-time dalam situasi tersebut.
Secara default, sinyal real-time yang tidak ditangani akan memperlakukan proses layaknya sinyal terminasi biasa (menghentikan proses tersebut). Perbedaan lainnya adalah jumlah dan penomoran sinyal. Standar POSIX menjamin setidaknya 8 sinyal real-time (POSIX_RTSIG_MAX ≥ 8), tetapi Linux menyediakan rentang yang lebih luas. Kernel Linux mendukung 33 sinyal real-time yang bernomor dari 32 hingga 64 (dengan nama simbolik SIGRTMIN
hingga SIGRTMAX
). Rentang pastinya dapat bervariasi – misal, library glibc
menggunakan dua sinyal real-time untuk keperluan internal threads sehingga SIGRTMIN
bisa bergeser (menjadi 34 atau 35). Oleh karena itu, program tidak seharusnya menggunakan angka absolut untuk sinyal real-time, melainkan menggunakan konstanta SIGRTMIN + n
yang dihitung saat run-time agar portabel di berbagai sistem. Berbeda dengan sinyal standar yang masing-masing memiliki tujuan atau makna khusus (misal SIGINT
untuk interupsi, SIGTERM
untuk terminasi, dll), seluruh kelompok sinyal real-time tidak memiliki makna yang telah ditetapkan sebelumnya – mereka disediakan untuk digunakan sesuai kebutuhan aplikasi. Dengan demikian, developer bebas memanfaatkan sinyal real-time ini untuk komunikasi atau sinkronisasi khusus antar proses sesuai logika aplikasi.
Jenis-Jenis Signal
Banyak jenis sinyal yang didefinisikan dalam POSIX dan sistem UNIX, masing-masing mewakili kondisi atau peristiwa berbeda. Secara garis besar, sinyal-sinyal ini dapat dikategorikan berdasarkan sumber atau efeknya.
Dari sisi sumber pemicu, ada sinyal yang dipicu oleh kesalahan program (synchronous), misalnya kesalahan segmentasi memori atau instruksi ilegal, yang dikirim oleh kernel tepat saat error terjadi. Ada juga sinyal yang berasal dari peristiwa eksternal (asynchronous) di luar alur eksekusi program, misalnya interupsi oleh pengguna (menekan Ctrl+C) atau aksi dari proses lain (seperti menggunakan perintah kill
). Sinyal asynchronous dapat terjadi kapan saja selama program berjalan, terlepas dari instruksi yang sedang dieksekusi, sedangkan sinyal synchronous terjadi sebagai konsekuensi langsung dari instruksi program yang berjalan (misal menjalankan instruksi ilegal langsung memicu sinyal ilegal).
Sinyal juga dapat dikelompokkan berdasarkan aksi default-nya terhadap proses (lihat kolom "Action" pada tabel sinyal). Sebagai contoh, banyak sinyal yang default-nya akan menghentikan proses (Termination), beberapa sinyal menyebabkan core dump selain menghentikan proses (Core dump), beberapa sinyal secara default diabaikan oleh proses (Ignore), dan ada pula sinyal yang akan memberhentikan sementara proses (Stop) atau melanjutkan proses (Continue). Misalnya, SIGTERM
memiliki default aksi terminate (menghentikan program), SIGSEGV
default-nya core dump (terminate + buat core file), SIGCHLD
default-nya ignore (diabaikan), SIGSTOP
default-nya stop, dan SIGCONT
default-nya continue. Program dapat mengganti aksi default ini (menangani atau mengabaikan sinyal) dengan memasang handler, kecuali untuk dua sinyal khusus: SIGKILL
dan SIGSTOP
tidak dapat dicegah penanganannya (tidak bisa di-catch, diblok, maupun diabaikan oleh proses).
Berikut adalah beberapa jenis sinyal standar beserta fungsinya dan kapan sinyal tersebut muncul atau digunakan:
-
SIGHUP, SIGINT, SIGQUIT, SIGTERM – Merupakan sinyal-sinyal untuk meminta terminasi atau interupsi proses yang umumnya dikirim oleh terminal atau sistem kepada program:
- SIGHUP (Hangup): Terjadi ketika terminal kontrol suatu proses ditutup atau koneksi hang-up. Sinyal ini sering digunakan juga untuk memberitahu proses daemon agar me-reload konfigurasi.
- SIGINT (Interrupt): Terjadi saat pengguna menekan
Ctrl+C
di terminal, menandakan interupsi dari keyboard. Default-nya menyebabkan program terhenti. - SIGQUIT (Quit): Terjadi saat pengguna menekan
Ctrl+\
. Mirip dengan SIGINT namun biasanya menghasilkan core dump sebelum keluar. - SIGTERM (Termination): Sinyal terminasi normal yang dikirim untuk meminta sebuah proses berhenti. Perintah
kill
tanpa spesifikasi sinyal akan mengirim SIGTERM. Program dapat menangani SIGTERM untuk melakukan cleanup sebelum keluar.
-
SIGKILL – Sinyal untuk menghentikan proses secara paksa. Berbeda dari SIGTERM, sinyal ini tidak dapat ditangkap atau diabaikan oleh program. SIGKILL menjamin terminasi segera; OS akan langsung mematikan proses yang dituju. Sinyal ini digunakan ketika proses tidak merespons terminasi normal atau dalam keadaan hang, karena proses tidak punya kesempatan menolak SIGKILL. (Note: karena sifatnya, SIGKILL sebaiknya digunakan sebagai Last Resort).
-
SIGSTOP dan SIGTSTP – Keduanya merupakan sinyal untuk memberhentikan (men-suspend) eksekusi proses secara temporer:
- SIGSTOP: Sinyal stop paksa yang tidak dapat dicegah atau ditangani oleh proses (mirip SIGKILL tapi untuk pause).Ketika menerima SIGSTOP, proses akan berhenti (pause) dan tidak dijadwalkan berjalan hingga ada sinyal lanjut.
- SIGTSTP: Sinyal stop yang dipicu oleh user, biasanya ketika pengguna menekan
Ctrl+Z
di terminal. Berbeda dengan SIGSTOP, sinyal SIGTSTP dapat ditangani atau diabaikan oleh program (karena merupakan permintaan dari user, program diberi kesempatan misal menolak pause). Secara default, jika tidak di-handle, SIGTSTP akan menghentikan proses seperti halnya SIGSTOP. Setelah proses dihentikan oleh SIGSTOP atau SIGTSTP, proses tersebut dapat dilanjutkan kembali dengan sinyal SIGCONT (Continue). Sinyal SIGCONT membuat proses yang tadinya pause kembali aktif berjalan.
-
SIGCONT – Sinyal untuk melanjutkan eksekusi proses yang sedang dalam keadaan terhenti (stop).Sinyal ini dikirim, misalnya, setelah mengirim SIGSTOP/SIGTSTP untuk memulai lagi proses tersebut tanpa memulai ulang program. Jika proses tidak sedang terhenti, SIGCONT biasanya diabaikan. (Note: SIGCONT tidak dapat diblokir oleh proses, agar proses selalu bisa dilanjutkan).
-
SIGCHLD – Sinyal yang dikirim kepada proses parent ketika sebuah proses child berhenti atau berakhir (terminate). Dengan kata lain, setiap kali child process selesai atau dihentikan (misal mendapat SIGSTOP), kernel akan mengirim SIGCHLD ke parent-nya. Secara default sinyal ini diabaikan (Ign), namun biasanya program parent akan memasang handler atau memeriksa SIGCHLD untuk melakukan
wait()
pengambilan status exit child atau tindakan lain ketika child selesai. Contoh penggunaan: reaping zombie process dilakukan saat menerima SIGCHLD. -
SIGALRM – Sinyal alarm waktu. Sinyal ini dikirim ke suatu proses ketika timer internal yang diatur oleh fungsi
alarm()
telah habis waktunya (kedaluwarsa). Default aksi SIGALRM adalah menghentikan proses (terminate). SIGALRM sering digunakan untuk memberi batas waktu (timeout) pada operasi tertentu. Misalnya, sebuah program dapat memanggilalarm(5)
untuk mengirim SIGALRM 5 detik kemudian sebagai mekanisme timeout. -
SIGPIPE – Sinyal pipe rusak. Terjadi ketika sebuah proses mencoba menulis ke pipa atau stream IPC yang ujung penerimanya sudah tertutup atau tidak ada lagi proses yang membaca. Contohnya, jika proses A menulis ke pipe yang terhubung ke proses B, dan proses B sudah selesai/keluar, maka saat A menulis, kernel akan mengirim SIGPIPE ke A. Default-nya, SIGPIPE akan menghentikan proses yang mengalaminya (terminate). Tujuan sinyal ini adalah mencegah proses terus menulis ke kanal yang tidak tersambung. Program dapat menangani SIGPIPE jika ingin mengantisipasi kondisi ini (misal untuk mencegah terminasi mendadak dengan menonaktifkan penulisan lebih lanjut).
-
SIGUSR1 dan SIGUSR2 – Dua sinyal yang disediakan untuk penggunaan khusus oleh pengguna/aplikasi. Sinyal ini tidak memiliki makna/predefinisi dari sistem operasi selain memang diperuntukkan bagi programmer untuk mengirim dan menangani sesuai kebutuhan aplikasi. Misalnya, sebuah aplikasi dapat mendefinisikan SIGUSR1 untuk melakukan aksi X dan SIGUSR2 untuk aksi Y sesuai rancangan programmer. Jika tidak ditangani, default aksi kedua sinyal ini adalah menghentikan proses (seperti sinyal terminasi biasa). Karena sifatnya yang bebas, SIGUSR1/2 sering digunakan dalam aplikasi sebagai mekanisme komunikasi ringan atau trigger event custom antar proses.
-
Sinyal program error (SIGSEGV, SIGILL, SIGFPE, SIGABRT, dll) – Ini adalah kumpulan sinyal yang dikirim oleh kernel ketika program mengalami kesalahan serius selama eksekusi, biasanya mengindikasikan bug atau kondisi fatal pada program:
- SIGSEGV (Segmentation Fault): Terjadi saat program melakukan akses memori yang tidak valid atau melanggar proteksi memori (misalnya dereferensi pointer NULL atau mengakses alamat di luar ruang memori yang dialokasikan).
- SIGILL (Illegal Instruction): Terjadi jika CPU mencoba mengeksekusi instruksi mesin yang tidak valid. Ini bisa disebabkan file biner program yang korup atau eksekusi ke area data non-eksekusi, sehingga op-code tidak dikenal CPU.
- SIGFPE (Floating Point Exception): Terjadi saat program melakukan kesalahan aritmatika yang tidak diperbolehkan, contohnya pembagian dengan nol atau overflow pada operasi floating-point.
- SIGABRT (Abort): Sinyal yang dikirim ketika program memanggil fungsi abort(), biasanya digunakan program secara sengaja untuk menghentikan dirinya sendiri karena kondisi error yang tidak dapat ditangani dengan aman.
Sinyal-sinyal di atas semuanya tergolong sinyal serius yang secara default akan menghentikan program dan menghasilkan core dump (file citra memori saat crash) untuk membantu debugging. Karena itu, default aksi untuk SIGSEGV, SIGILL, SIGFPE, SIGABRT adalah Core (terminate + core dump). Program dapat memasang handler untuk mencoba menangani error (contoh: mencetak pesan lalu keluar bersih), tetapi seringkali setelah sinyal ini terjadi, lingkungan program sudah tidak konsisten sehingga melanjutkan eksekusi berisiko.
-
Sinyal Real-Time (SIGRTMIN hingga SIGRTMAX) – Selain sinyal standar di atas, Linux menyediakan rentang sinyal real-time bernomor SIGRTMIN sampai SIGRTMAX. Di Linux, biasanya rentang ini bernilai 32 hingga 64 (total 33 sinyal). Sinyal real-time tidak memiliki makna khusus secara default (tidak ditetapkan untuk kondisi spesifik apapun). Artinya, semua sinyal real-time dapat digunakan secara bebas oleh aplikasi untuk tujuan komunikasi atau sinkronisasi kustom. Keunggulan sinyal real-time dibanding sinyal standar antara lain: dapat mengantri jika terkirim berulang kali dalam keadaan diblokir, sehingga tidak ada sinyal yang “hilang”, dan dapat membawa informasi tambahan (seperti nilai integer atau pointer) yang dikirim bersama sinyal (dengan menggunakan
sigqueue()
dan menerima di handler melaluisiginfo_t
). Selain itu, bila beberapa sinyal real-time berbeda jenis dikirim ke proses, sinyal tersebut akan diterima secara berurutan sesuai nomor sinyalnya (nomor lebih rendah memiliki prioritas lebih tinggi). Sinyal real-time sangat bermanfaat untuk aplikasi yang membutuhkan banyak sinyal kustom tanpa khawatir bertabrakan dengan sinyal standar atau kehilangan sinyal saat traffic sinyal tinggi.
Fungsi-Fungsi Penting dalam Penanganan Signal
Untuk berinteraksi dengan mekanisme sinyal (menangani atau mengirim sinyal) dalam pemrograman C/POSIX, tersedia sejumlah fungsi penting sebagai interface. Fungsi-fungsi ini meliputi pemasangan handler, pengiriman sinyal, dan manipulasi perilaku sinyal. Berikut beberapa fungsi utama beserta kegunaannya:
- signal() – Fungsi dasar untuk menetapkan handler sederhana suatu sinyal. Prototipe
signal(int signum, sighandler_t handler)
dideklarasikan di<signal.h>
. Dengansignal()
, program dapat menentukan bagaimana menanggapi sinyal bernomorsignum
dengan aksi tertentu. Parameter kedua dapat berupa konstantaSIG_DFL
(gunakan aksi default),SIG_IGN
(abaikan sinyal tersebut) atau pointer ke fungsi buatan pengguna yang akan dijalankan sebagai handler. Sebagai contoh,signal(SIGINT, handler_int)
akan menyebabkan fungsihandler_int
dipanggil ketika SIGINT diterima. Note, meskipun mudah digunakan,signal()
memiliki beberapa kekurangan (non-portable atau perilaku bervariasi pada sistem berbeda saat memasang handler). POSIX lebih menganjurkan penggunaansigaction()
untuk penanganan sinyal yang lebih konsisten. Namun,signal()
tetap sering digunakan untuk keperluan sederhana atau kompatibilitas C lama. - sigaction() – Sistem call yang lebih canggih untuk mengatur aksi ketika sinyal diterima. Melalui
sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
, program dapat menginstall handler baru untuk suatu sinyal dan sekaligus mengatur opsi lanjutan. Fungsi ini memungkinkan pengaturan handler seperti halnyasignal()
, namun dengan kontrol lebih: dapat menentukan mask sinyal apa saja yang diblok selama handler berjalan, jenis informasi yang diterima handler (menggunakan flag SA_SIGINFO untuk mendapatkan detail tambahan viasiginfo_t
), serta berbagai flag perilaku (misal SA_RESTART, SA_NOCLDSTOP, dll).sigaction()
adalah cara standar POSIX untuk penanganan sinyal yang andal. Satu batasan penting: kita tidak bisa mengubah penanganan untuk sinyal SIGKILL maupun SIGSTOP menggunakan fungsi ini (kedua sinyal tersebut memang tidak dapat ditangani oleh proses). Dengansigaction()
, developer dapat menghindari beberapa masalah potensial yang ada padasignal()
dan memperoleh kompatibilitas lintas platform yang lebih baik. - kill() – Meskipun namanya "kill", fungsi ini sebenarnya digunakan untuk mengirim sinyal apapun ke proses lain. Prototipe
int kill(pid_t pid, int sig)
akan mengirim sinyal bernomorsig
ke proses dengan PID yang ditentukan olehpid
. Jikapid
bernilai positif, sinyal dikirim ke proses dengan PID tersebut. Fungsi ini umumnya digunakan untuk meminta terminasi proses (misalkill(pid, SIGTERM)
ataukill(pid, SIGKILL
) untuk menghentikan proses), namun dapat pula mengirim jenis sinyal lainnya sesuai kebutuhan (misalkill(pid, SIGUSR1)
untuk memberi sinyal custom ke proses lain). Secara internal, kernel akan memeriksa izin dimana hanya proses yang memiliki izin (proses milik user yang sama atau superuser) yang dapat mengirim sinyal ke proses target.Jika pengiriman berhasil,kill()
mengembalikan 0, sedangkan jika gagal (misal PID tidak ditemukan atau tanpa izin) akan mengembalikan -1 dengan errno yang sesuai. Perintah shellkill
juga memanfaatkan sistem callkill()
ini di balik layar. - raise() - Fungsi untuk mengirim sinyal ke proses itu sendiri (proses pemanggil). Prototipenya
int raise(int sig)
.raise()
pada dasarnya membuat program seolah-olah mengirim sinyal kepada dirinya sendiri. Dalam program single-threaded,raise(sig)
ekuivalen dengan memanggilkill(getpid(), sig)
, yaitu mengirim sinyalsig
ke prosesnya sendiri. Pada program multithreading,raise()
akan mengirim sinyal ke thread yang memanggil fungsi tersebut (mirip denganpthread_kill(pthread_self(), sig)
). Fungsi ini berguna ketika program ingin men-trigger penanganan sebuah sinyal secara mandiri. Misalnya, program bisa memanggilraise(SIGTERM)
untuk memicu rutinitas penanganan SIGTERM dalam dirinya, seolah-olah sinyal tersebut datang dari OS.raise()
mengembalikan 0 jika sukses mengirim (yaitu seharusnya selalu sukses kecuali jika sig tidak valid), dan akan menjalankan handler jika ada (fungsi raise() baru kembali setelah handler selesai dijalankan, apabila sinyal tersebut ditangani).
Selain fungsi-fungsi di atas, ada juga fungsi lain terkait sinyal. Misalnya, pause()
dapat digunakan untuk membuat proses tidur sampai suatu sinyal diterima, dan fungsi-fungsi seperti sigprocmask()
/ sigpending()
untuk memblokir sementara sinyal atau memeriksa sinyal tertunda. Namun, hal-hal tersebut berada di luar lingkup dasar teori ini. Secara ringkas, kombinasi mekanisme sinyal dan fungsi-fungsi penanganannya memberikan cara bagi proses untuk berkomunikasi, mengontrol, dan merespons kejadian asynchronous di sistem operasi. Dengan pemahaman tentang jenis-jenis sinyal serta penggunaan fungsi signal()
, sigaction()
, kill()
, dan raise()
, seorang developer dapat mengimplementasikan penanganan sinyal yang tepat dalam program, seperti menangani interupsi Ctrl+C, menghentikan proses dengan aman, atau berkomunikasi antar proses melalui sinyal.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void waitForSignal();
void sigint_handler(int sig);
int main() {
waitForSignal();
return 0;
}
void waitForSignal() {
// Menampilkan current working directory
char cwd[1024];
getcwd(cwd, sizeof(cwd));
printf("Current working directory: %s\n", cwd);
fflush(stdout);
// Memasang handler untuk SIGINT (Ctrl+C)
signal(SIGINT, sigint_handler);
printf("Waiting for signal (Press Ctrl+C to trigger SIGINT)...\n");
// Proses akan menunggu sinyal datang
pause();
// Pesan setelah pause() berakhir (tidak akan tampil pada contoh ini)
printf("Hello\n");
}
// Handler yang dipanggil saat menerima SIGINT
void sigint_handler(int sig) {
printf("\nSignal %d (SIGINT) received!\n", sig);
printf("Entering infinite loop...\n");
// Infinite loop untuk simulasi handler yang tidak selesai
while(1) {
printf("apple\n");
sleep(1);
}
}